package com.jichangxiu.framework.config;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.jichangxiu.common.constant.Constants;
import com.jichangxiu.framework.factory.CaptchaFactory;
import com.jichangxiu.framework.filter.AuthFilter;
import com.jichangxiu.framework.filter.CaptchaFilter;
import com.jichangxiu.framework.filter.LoginFilter;
import com.jichangxiu.framework.handler.LogoutHandler;
import com.jichangxiu.framework.point.AuthEntryPoint;
import com.jichangxiu.framework.provider.AuthProvider;
import com.jichangxiu.framework.service.AuthUserDetailsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {

    private final CaptchaFilter captchaFilter = CaptchaFactory.captchaFilter();

    private final AuthFilter authFilter = SpringUtil.getBean(AuthFilter.class);

    private final AuthUserDetailsService authUserDetailsService = SpringUtil.getBean(AuthUserDetailsService.class);

    private final PasswordEncoder passwordEncoder = SpringUtil.getBean(PasswordEncoder.class);

    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public LoginFilter loginFilter() {
        log.debug("【SecurityConfig】加载【LoginFilter】配置");
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationManager(authenticationManager());
        loginFilter.setFilterProcessesUrl(Constants.JCX_PROPERTIES.getSecurityProperties().getLoginProcessingUrl());
        return loginFilter;
    }

    public AuthProvider authProvider() {
        log.debug("【SecurityConfig】加载【AuthProvider】配置");
        return new AuthProvider(authUserDetailsService, passwordEncoder);
    }

    public AuthenticationManager authenticationManager() {
        log.debug("【SecurityConfig】加载【AuthenticationManager】配置");
        List<AuthenticationProvider> providerList = new ArrayList<>();
        providerList.add(authProvider());
        ProviderManager providerManager = new ProviderManager(providerList);
        providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));
        return providerManager;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        log.debug("【SecurityConfig】加载【SecurityFilterChain】配置");
        String noAuthPath = Constants.JCX_PROPERTIES.getSecurityProperties().getNoAuthPath();
        String noAuthStaticPath = Constants.JCX_PROPERTIES.getSecurityProperties().getNoAuthStaticPath();
        // 关闭跨站
        http.cors().and().csrf().disable();
        // 登录配置
        http.httpBasic().and().formLogin().loginProcessingUrl(Constants.JCX_PROPERTIES.getSecurityProperties().getLoginProcessingUrl())
                .and().logout().logoutUrl(Constants.JCX_PROPERTIES.getSecurityProperties().getLogoutProcessingUrl())
                .logoutSuccessHandler(new LogoutHandler());
        // 禁用 session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 配置拦截规则
        http.authorizeRequests()
                // 放行预检请求
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                // 允许匿名访问
                .antMatchers(StrUtil.isNotEmpty(noAuthPath) ? noAuthPath.split(",") : new String[]{}).permitAll()
                // 放行静态资源
                .antMatchers(HttpMethod.GET, StrUtil.isNotEmpty(noAuthStaticPath) ? noAuthStaticPath.split(",") : new String[]{}).permitAll()
                // 其他请求都需要经过验证
                .anyRequest().authenticated();
        // 异常处理器
        http.exceptionHandling().authenticationEntryPoint(new AuthEntryPoint());
        // 配置自定义的过滤器
        http.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class);
        // 验证码过滤器放在 UsernamePassword 过滤器之前
        if (ObjectUtil.isNotEmpty(captchaFilter)) {
            http.addFilterBefore(captchaFilter, AbstractPreAuthenticatedProcessingFilter.class);
            // 自定义登陆方式
            http.addFilterAfter(loginFilter(), CaptchaFilter.class);
        } else {
            // 自定义登陆方式
            http.addFilterBefore(loginFilter(), AbstractPreAuthenticatedProcessingFilter.class);
        }
        return http.build();
    }

}
